//  @ Project : GagalPanen
//  @ File Name : gridmap.cpp
//  @ Date : 3/9/2011
//  @ Author : Denis Irvan
//
//

#include <vector>
#include <queue>
#include <assert.h>

#include "gridmap.hpp"
#include "direction.hpp"
#include "nopathfoundexception.cpp"
#include "plantablecell.hpp"

GridMap::GridMap(GameInterface* game, std::vector<std::string> map) :
    Map(game), grid_(game, map) {

}

GridMap::GridMap(GameInterface* game, std::ifstream& is) :
    Map(game, is), grid_(game, is) {

}

void GridMap::save(std::ofstream& os) {
  Map::save(os);
  grid_.save(os);
}

GridMap::~GridMap() {}

bool GridMap::containsCell(Cell* cell) {
  return getGrid()->containsCell(cell);
}

void GridMap::advanceDay() {
  getGrid()->advanceDay();
  return;
}

void GridMap::advanceTime() {
  getGrid()->advanceTime();
  return;
}

Cell* GridMap::getCellInFrontOff(Cell* cell, Direction dir) {
  assert(getGrid()->containsCell(cell));
  return getGrid()->getCellInFrontOff(cell, dir);
}

Cell* GridMap::getCellAt(int x, int y) {
  assert(x >= 0 && x < getGrid()->getColumn());
  assert(y >= 0 && y < getGrid()->getRow());
  return getGrid()->getCellAt(x, y);
}

std::vector<ActionType> GridMap::getShortestPathBetween(
      Cell* init_cell, Direction init_direction,
      Cell* destination_cell, Direction dest_direction) {
  assert(isReachable(init_cell, init_direction,
          destination_cell, dest_direction));
  return sensitiveShortestPathBetween(init_cell, init_direction,
          destination_cell, dest_direction);
}

bool GridMap::isReachable(
      Cell* init_cell, Direction init_direction,
      Cell* destination_cell, Direction dest_direction) {
  try {
    sensitiveShortestPathBetween(init_cell, init_direction,
        destination_cell, dest_direction);
  } catch (NoPathFoundException ex) {
    return false;
  }
  return true;
}

Grid* GridMap::getGrid() {
  return &grid_;
}

std::vector<ActionType> GridMap::sensitiveShortestPathBetween(
      Cell* init_cell, Direction init_direction,
      Cell* destination_cell, Direction dest_direction) {

  std::vector< std::vector< std::vector< std::vector<ActionType> > > >
          shortest_path(
          // column
            getGrid()->getColumn(),
            std::vector< std::vector< std::vector< ActionType> > >(
              // row
              getGrid()->getRow(),
              std::vector< std::vector< ActionType > >(
                // directions
                DirectionNamespace::getDirectionCount(),
                std::vector< ActionType >()
              )
            )
          );

  // shortest_path[a][b][c] is self-explanatory

  std::vector< std::vector< std::vector< bool > > >
          is_done(
           // column
            getGrid()->getColumn(),
            std::vector< std::vector< bool> >(
              // row
              getGrid()->getRow(),
              std::vector< bool > (
                // directions
                DirectionNamespace::getDirectionCount(),
                false
              )
            )
          );

  // is_done[a][b][c] is true iff row a, column b, dir c has been accounted for

  int init_x = init_cell->getX();
  int init_y = init_cell->getY();
  int init_dir = (int)init_direction;
  int target_x = destination_cell->getX();
  int target_y = destination_cell->getY();
  int target_dir = (int)dest_direction;

  is_done[init_x][init_y][init_dir] = true;

  std::queue<int> top_x, top_y, top_dir;

  top_x.push(init_x);
  top_y.push(init_y);
  top_dir.push(init_dir);

  while (!top_x.empty()) {
    int tx = top_x.front();
    int ty = top_y.front();
    int td = top_dir.front();
    top_x.pop();
    top_y.pop();
    top_dir.pop();

    // rotate in place
    for (int j = 0; j < 4; ++j) {
      if (!is_done[tx][ty][j]) {
        is_done[tx][ty][j] = true;
        shortest_path[tx][ty][j] = shortest_path[tx][ty][td];

        // find the correct rotation
        shortest_path[tx][ty][j].push_back(ActionType((j - td + 4) % 4));
        top_x.push(tx);
        top_y.push(ty);
        top_dir.push(j);
      }
    }

    // move to front
    int newx = tx, newy = ty, newdir = td;
    newx += DirectionNamespace::getDeltaX(Direction(td));
    newy += DirectionNamespace::getDeltaY(Direction(td));

    if (getCellAt(newx, newy)->isSteppable()) {
      if (!is_done[newx][newy][newdir]) {
        is_done[newx][newy][newdir] = true;
        shortest_path[newx][newy][newdir] = shortest_path[tx][ty][td];
        shortest_path[newx][newy][newdir].push_back(kActionUp);
        top_x.push(newx);
        top_y.push(newy);
        top_dir.push(newdir);
      }
    }
  }

  if (!is_done[target_x][target_y][target_dir]) {
    throw NoPathFoundException();
  }
  return shortest_path[target_x][target_y][target_dir];
}


void GridMap::burnAllPlantableCells() {
  for (int i = 0; i < getGrid()->getColumn(); ++i) {
    for (int j = 0; j < getGrid()->getRow(); ++j) {
      if (getCellAt(i,j)->isPlantable()) {
        PlantableCell* pc = (PlantableCell*) getCellAt(i,j);
        pc->burn();
      }
    }
  }
}
